﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.IO;
using System.Text;
using System.Web;

using Akabana.Amf.Util;
using Akabana.Amf.Serializer;
using Akabana.Amf.Messaging.Gateway.Configuration;
using Akabana.Amf.Messaging.Data;
using Akabana.Amf.Messaging.IO;
using Akabana.Amf.Messaging.Messages;

using log4net;

namespace Akabana.Amf.Messaging.Gateway.Service
{
    internal sealed class MessagingServiceProcessor
    {

        private static ILog logger = LogManager.GetLogger(typeof(MessagingServiceProcessor));

        private static MessagingServiceProcessor _Instance = null;

        public static MessagingServiceProcessor NewInstance()
        {
            MessagingServiceProcessor result;
            if (_Instance == null)
            {
                AssemblyLoader.Initialize();
                logger.Info("configure...");

                MessagingGatewayConfiguration.Configure();
                logger.Info("configured.");

                logger.Info("initializing...");

                result = new MessagingServiceProcessor();
                result._ServiceInvoker = CreateServiceInvoker(MessagingGatewayConfiguration.Settings.Assemblies);

                logger.Info("ready.");
            }
            else
            {
                result = _Instance;
            }

            return result;
        }

        private static MessagingServiceMethodInvoker CreateServiceInvoker(IList assemblies)
        {
            var serviceInvoker = new MessagingServiceMethodInvoker();

            foreach (AssemblySettings assemblySettings in assemblies)
            {
                try
                {
                    var assembly = Assembly.Load(assemblySettings.Type);
                    serviceInvoker.AddAssembly(assembly);
                    AssemblyLoader.RegisterAssembly(assembly);
                    logger.Debug("Loaded Assembly is " + assemblySettings.Type);
                }
                catch (System.Exception e)
                {
                    logger.Error(e.Message, e);
                }
            }
            return serviceInvoker;
        }

        private MessagingServiceMethodInvoker _ServiceInvoker;

        private MessagingServiceProcessor()
        {
        }

        public void Process(HttpRequest request, HttpResponse response)
        {
            if (request.InputStream.Length > 0)
            {
                try
                {
                    response.Clear();
                    HttpHeaderHelper.setAmfContentType(response);
                    HttpHeaderHelper.setCacheControl(response);

                    logger.Debug("request parsing...");
                    var requestMessage = DoParseRequest(request);
                    logger.Debug("request is " + requestMessage);

                    var responseMessage = requestMessage.CreateResponse();
                    logger.Debug("processing...");

                    DoProcessMessage(requestMessage, responseMessage);
                    logger.Debug("process result is " + responseMessage);

                    logger.Debug("responseAmfMessage sendback...");
                    DoSendBackResponse(response, responseMessage);

                    logger.Debug("messaging done.");
                }
                catch (System.Exception e)
                {
                    logger.Error(e.Message, e);
                    throw e;
                }
            }
        }

        private void DoSendBackResponse(HttpResponse httpResponse, AmfMessage responseAmfMessage)
        {
            var writer = new AmfMessageWriter(new BufferedStream(httpResponse.OutputStream, 2048),responseAmfMessage.version);
            writer.WriteVersion(responseAmfMessage.version);
            writer.WriteHeaderCount(responseAmfMessage.headerCount);
            writer.WriteBodyCount(responseAmfMessage.bodyCount);

            for (var i = 0; i < responseAmfMessage.bodyCount; i++)
            {
                var body = responseAmfMessage.GetBodyAt(i);
                writer.WriteTarget(body.target);
                writer.WriteResponse(body.response);
                writer.WriteMessageSize(0xFFFFFFFF);
                writer.WriteObject(body.data);
            }
            writer.Flush();
        }

        private AmfMessage DoParseRequest(HttpRequest httpRequest)
        {
            var reader = new AmfMessageReader(new BufferedStream(httpRequest.InputStream, 2048));
            
            var result = AmfMessage.CreateInstance(reader.version);

            var headerCount = reader.ReadHeaderCount();
            var bodyCount = reader.ReadBodyCount();
            result.Initialize(headerCount, bodyCount);

            var session = HttpContext.Current.Session;

            logger.Debug("sessionId=" + session.SessionID + ",IsCookieless=" + session.IsCookieless + ",IsNewSession=" + session.IsNewSession);
            logger.Debug("DSId=" + session["DSId"]);

            logger.Debug("version is " + result.version);
            logger.Debug("headerCount is " + result.headerCount);
            logger.Debug("bodyCount is " + result.bodyCount);

            for (var i = 0; i < result.bodyCount; i++)
            {
                string target = reader.ReadTarget();
                string response = reader.ReadResponse();

                var body = AmfMessageBody.CreateInstance(target, response);
                uint messageSize = reader.ReadMessageSize();

                logger.Debug("target is " + body.target);
                logger.Debug("responseAmfMessage is " + body.response);

                logger.Debug("messageSize is " + messageSize);

                var amfData = reader.ReadObject();
                body.data = amfData;

                result.AddBody(body);
            }

            return result;
        }

        private void DoProcessMessage(AmfMessage request, AmfMessage response)
        {
            bool isMessageData;
            object requestArrayDataElement;
            AbstractMessage requestMessage = null;
            AmfMessageBody requestBody;
            AmfMessageBody responseBody;                
            for (var i = 0; i < request.bodyCount; i++)
            {
                isMessageData = false;
                requestArrayDataElement = null;
                responseBody = null;

                requestBody = request.GetBodyAt(i);
                if (requestBody.data is Array)
                {
                    var requestArrayData = requestBody.data as Array;
                    if (requestArrayData.Length >= 1)
                    {
                        requestArrayDataElement = requestArrayData.GetValue(i);
                        if (requestArrayDataElement is AbstractMessage)
                        {
                            requestMessage = requestArrayDataElement as AbstractMessage;
                            isMessageData = true;
                        }
                    }
                }

                try
                {
                    if (isMessageData)
                    {
                        if (requestMessage is CommandMessage)
                        {
                            responseBody = requestBody.CreateResultResponse();
                            responseBody.data = requestMessage.CreateAcknowledgeMessage(null);
                        }
                        else if (requestMessage is RemotingMessage)
                        {
                            var remotingMessage = requestArrayDataElement as RemotingMessage;
                            responseBody = requestBody.CreateResultResponse();
                            responseBody.data = requestMessage.CreateAcknowledgeMessage( _ServiceInvoker.Invoke(remotingMessage.destination + "." + remotingMessage.operation, remotingMessage.body));
                        }
                    }
                    else
                    {
                        responseBody = requestBody.CreateResultResponse();
                        responseBody.data = _ServiceInvoker.Invoke(requestBody.target, requestBody.data);
                    }
                }
                catch (System.Exception e)
                {
                    if (isMessageData)
                    {
                        responseBody = requestBody.CreateStatusResponse();
                        responseBody.data = (requestBody.data as CommandMessage).CreateErrorMessage(e);
                    }
                    else
                    {
                        responseBody = requestBody.CreateStatusResponse();
                        responseBody.data = Fault.CreateFault(e);
                    }
                }
                finally
                {
                    response.AddBody(responseBody);
                }
            }
        }
    }
}
